package com.gframework.mybatis.dao.mybatis.provider;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import javax.persistence.Column;
import javax.persistence.Id;
import javax.persistence.Table;

import com.gframework.mybatis.dao.IMybatisDAO;
import com.gframework.mybatis.dao.IPrimaryDAO;
import com.gframework.mybatis.dao.ITableBasicDAO;
import com.gframework.util.GStringUtils;
import com.gframework.util.ReflectUtils;

/**
 * sql提供器操作辅助工具类，主要是进行POJO类的解析操作.
 * 
 * @since 1.0.0
 * @author Ghwolf
 */
public class ProviderUtils {

	private ProviderUtils() {
	}

	/**
	 * 用来缓存POJO类解析数据的集合.
	 * 
	 * <pre>
	 * key：pojo类型
	 * value：pojo解析数据
	 * </pre>
	 */
	private static final Map<Class<?>, PojoInfo> POJO_CACHE = new ConcurrentHashMap<>(128);
	/**
	 * 用来缓存MybatisDao接口解析数据的集合.
	 * 
	 * <pre>
	 * key：mybatis dao接口类型
	 * value：dao接口解析数据
	 * </pre>
	 */
	private static final Map<Class<?>, MybatisDaoInfo> MYBATIS_DAO_CACHE = new ConcurrentHashMap<>(128);

	/**
	 * 根据一个pojo实体类所描述的信息，取得PojoInfo类对象，包含了pojo类解析信息.
	 * 
	 * @param pojoType POJO实体类类型
	 * @return 返回 {@link PojoInfo} 类对象
	 * @throws PojoAnalysisException 解析POJO类出错时抛出异常。如重复的列等。
	 * @throws NullPointerException 如果参数为null，则抛出此异常
	 */
	public static PojoInfo getPojoInfo(Class<?> pojoType) {
		PojoInfo vo = POJO_CACHE.get(pojoType);
		if (vo == null) {
			return initPojo(pojoType);
		} else {
			return vo;
		}
	}

	/**
	 * 根据一个mybatis的dao接口（此接口是基于gframework自动生成框架所规范化的接口）类型，解析出
	 * {@link MybatisDaoInfo} 类对象，其中包含的有 {@link PojoInfo}类信息
	 * 
	 * @param daoType POJO实体类类型
	 * @return 返回 {@link MybatisDaoInfo} 类对象
	 * @throws PojoAnalysisException 解析POJO类或dao类出错时抛出异常。如重复的列或接口不正确等。
	 * @throws NullPointerException 如果参数为null，则抛出此异常
	 */
	public static MybatisDaoInfo getMybatisDaoInfo(Class<?> daoType) {
		MybatisDaoInfo vo = MYBATIS_DAO_CACHE.get(daoType);
		if (vo == null) {
			return initMybatisDao(daoType);
		} else {
			return vo;
		}
	}

	/**
	 * 解析一个mybaits dao接口信息
	 */
	@SuppressWarnings("unchecked")
	private static MybatisDaoInfo initMybatisDao(Class<?> daoType) {
		if (daoType == null) {
			throw new NullPointerException();
		}
		synchronized (daoType) {
			MybatisDaoInfo vo = MYBATIS_DAO_CACHE.get(daoType);
			if (vo != null) {
				return vo;
			}
			vo = new MybatisDaoInfo();
			Class<? extends Serializable> pojoType;
			if (IMybatisDAO.class.isAssignableFrom(daoType)) {
				Class<?> cls = ReflectUtils.getParameterizedClass(daoType, IMybatisDAO.class, 0);
				if (!Serializable.class.isAssignableFrom(cls)) {
					throw new PojoAnalysisException("解析出错！" + daoType.getName() + " 类实现的 " + IMybatisDAO.class.getName()
							+ " 接口设置的实体类类型不是Serializable接口的子类，实际为：" + cls.getName());
				}
				pojoType = (Class<? extends Serializable>) cls;
			} else {
				throw new PojoAnalysisException(
						daoType.getName() + " 类不是 " + IMybatisDAO.class.getName() + " 接口的子类，无法解析。");
			}
			vo.setReadOnly(!ITableBasicDAO.class.isAssignableFrom(daoType));
			vo.setPojoInfo(getPojoInfo(pojoType));
			vo.setPojoType(pojoType);

			if (IPrimaryDAO.class.isAssignableFrom(daoType)) {
				Class<?> pkType = ReflectUtils.getParameterizedClass(daoType, IPrimaryDAO.class, 0);
				if (!Serializable.class.isAssignableFrom(pkType)) {
					throw new PojoAnalysisException("解析出错！" + daoType.getName() + " 类实现的 " + IPrimaryDAO.class.getName()
							+ " 接口设置的主键类类型不是Serializable接口的子类，实际为：" + pkType.getName());
				}
				vo.setPrimaryKeyType((Class<? extends Serializable>) pkType);
			}

			MYBATIS_DAO_CACHE.put(daoType, vo);
			return vo;
		}
	}

	/**
	 * 根据一个pojo实体类所描述的信息，取得对应表所有的列名称.
	 * <p>
	 * 需要注意的是，返回的集合是一个数据库中的名称，而非属性名称。可以使用{@link Column}注解指定列名称，
	 * 如果没有指定，则会采用驼峰命名转换原则进行转换。
	 * </p>
	 * <p>
	 * 只有成员属性才会被解析处理，静态属性会被忽略。
	 * 同时某些属性不想被解析，可以使用{@link javax.persistence.Transient}注解进行忽略配置。
	 * </p>
	 * 
	 * @param pojoType POJO实体类类型
	 * @return 返回列信息集合
	 * @throws PojoAnalysisException 解析POJO类出错时抛出异常。如重复的列等。
	 * @throws NullPointerException 如果参数为null，则抛出此异常
	 */
	public static String[] getColumnNames(Class<?> pojoType) {
		PojoInfo vo = POJO_CACHE.get(pojoType);
		if (vo == null) {
			vo = initPojo(pojoType);
		}
		return vo.getColumNames();
	}

	/**
	 * 根据pojo实体类取得对应表的表名称.
	 * <p>
	 * 可以使用{@link Table}注解指定表名称，
	 * 如果没有指定，则会采用驼峰命名转换原则进行转换。
	 * </p>
	 * 
	 * @param pojoType POJO实体类类型
	 * @return 返回表名称
	 * @throws PojoAnalysisException 解析POJO类出错时抛出异常。如重复的列等。
	 * @throws NullPointerException 如果参数为null，则抛出此异常
	 */
	public static String getTableName(Class<?> pojoType) {
		PojoInfo vo = POJO_CACHE.get(pojoType);
		if (vo == null) {
			vo = initPojo(pojoType);
		}
		return vo.getTableName();
	}

	/**
	 * 根据pojo实体类取得对应表的主键列名称.
	 * <p>
	 * 如果POJO没有通过 {@link Id} 注解指定主键，则会认为时没有主键。此时将会返回null。
	 * 目前不支持也不建议多主键形式。
	 * </p>
	 * 
	 * @param pojoType POJO实体类类型
	 * @return 主键列名称，没有则为null，但返回值一定不是null，获取到的列名称可能为null
	 * @throws PojoAnalysisException 解析POJO类出错时抛出异常。如重复的列等。
	 * @throws NullPointerException 如果参数为null，则抛出此异常
	 */
	public static Optional<String> getPKColumnName(Class<?> pojoType) {
		PojoInfo vo = POJO_CACHE.get(pojoType);
		if (vo == null) {
			vo = initPojo(pojoType);
		}
		return Optional.ofNullable(vo.getPkColumnName());
	}

	/**
	 * 解析一个POJO类
	 */
	private static PojoInfo initPojo(Class<?> pojoType) {
		if (pojoType == null) {
			throw new NullPointerException();
		}
		synchronized (pojoType) {
			PojoInfo vo = POJO_CACHE.get(pojoType);
			if (vo != null) {
				return vo;
			}
			vo = new PojoInfo();
			analysisColumnNames(pojoType, vo);
			analysisTableName(pojoType, vo);
			POJO_CACHE.put(pojoType, vo);
			return vo;
		}
	}

	/**
	 * 解析列名称和主键
	 */
	private static void analysisColumnNames(Class<?> pojoType, PojoInfo vo) {
		Class<?> cls = pojoType;
		List<String> columnNames = new ArrayList<>();
		List<String> fieldNames = new ArrayList<>();
		List<String> dynamicParamNames = new ArrayList<>();
		while (cls != Object.class) {
			for (Field f : cls.getDeclaredFields()) {
				if (Modifier.isStatic(f.getModifiers()) || f.getAnnotation(javax.persistence.Transient.class) != null
						|| f.getAnnotation(java.beans.Transient.class) != null) {
					continue;
				}

				Column col = f.getAnnotation(Column.class);
				String name;
				if (col == null || "".equals(col.name())) {
					name = GStringUtils.toMapUnderscore(f.getName());
				} else {
					name = col.name();
				}
				if (columnNames.contains(name)) {
					throw new PojoAnalysisException(pojoType.getName() + " 类存在重复的列名称【" + name + "】在属性上：" + f.getName());
				}
				columnNames.add(name);
				fieldNames.add(f.getName());
				dynamicParamNames.add("#{" + f.getName() + "}");

				if (f.getAnnotation(Id.class) != null) {
					if (vo.getPkColumnName() == null) {
						vo.setPkColumnName(name);
						vo.setPkFieldName(f.getName());
						vo.setHasPrimaryKey(true);
					} else {
						throw new PojoAnalysisException(
								pojoType.getName() + " 存在重复的主键列【" + vo.getPkColumnName() + "】，在属性上：" + f.getName());
					}
				}
			}
			cls = cls.getSuperclass();
		}
		vo.setColumNames(columnNames.toArray(new String[columnNames.size()]));
		vo.setFieldNames(fieldNames.toArray(new String[fieldNames.size()]));
		vo.setDynamicParamFieldNames(dynamicParamNames.toArray(new String[dynamicParamNames.size()]));
	}

	/**
	 * 解析表名称
	 */
	private static void analysisTableName(Class<?> pojoType, PojoInfo vo) {
		Table table = pojoType.getAnnotation(Table.class);
		String name;
		if (table == null || "".equals(table.name())) {
			name = GStringUtils.toMapUnderscore(pojoType.getSimpleName());
		} else {
			name = table.name();
		}
		vo.setTableName(name);
	}

}
